<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>一、什么是指令</h2>
  <div>指令系统是计算机硬件的语言系统，也叫机器语言，它是系统程序员看到的计算机的主要属性。因此指令系统表征了计算机的基本功能决定了机器所要求的能力</div>
  <br>
  <div>在vue中提供了一套为数据驱动视图更为方便的操作，这些操作被称为指令系统</div>
  <br>
  <div>我们看到的v-开头的行内属性，都是指令，不同的指令可以完成或实现不同的功能</div>
  <h2>二、如何实现</h2>
  <div>注册一个自定义指令有全局注册与局部注册</div>
  <br>
  <div>全局注册注册主要是用过Vue.directive方法进行注册</div>
  <br>
  <div>Vue.directive第一个参数是指令的名字（不需要写上v-前缀），第二个参数可以是对象数据，也可以是一个指令函数</div>
  <br>
  <div>局部注册通过在组件options选项中设置directive属性</div>
  <script>
    // 注3321册一个全局自定义指令 `v-focus`
    // Vue.directive('focus', {
    //   // 当被绑定的元素插入到 DOM 中时……
    //   inserted: function (el) {
    //     // 聚焦元素
    //     el.focus()  // 页面加载完成之后自动让输入框获取到焦点的小功能
    //   }
    // })


    // directives: {
    //   focus: {
    //     // 指令的定义
    //     inserted: function (el) {
    //       el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
    //     }
    //   }
    // }

    // 然后你可以在模板中任何元素上使用新的 v-focus property，如下：
    // <input v-focus />
  </script>
  <br>
  <div>自定义指令也像组件那样存在钩子函数：</div>
  <ul>
    <li>bind：只调用一次，指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置</li>
    <li>inserted：被绑定元素插入父节点时调用 (仅保证父节点存在，但不一定已被插入文档中)</li>
    <li>update：所在组件的 VNode 更新时调用，但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变，也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新</li>
    <li>componentUpdated：指令所在组件的 VNode 及其子 VNode 全部更新后调用</li>
    <li>unbind：只调用一次，指令与元素解绑时调用</li>
  </ul>
  <div>所有的钩子函数的参数都有以下：</div>
  <ul>
    <li>el：指令所绑定的元素，可以用来直接操作 DOM</li>
    <li>binding：一个对象，包含以下 property：</li>
    <ul>
      <li>name：指令名，不包括 v- 前缀。</li>
      <li>value：指令的绑定值，例如：v-my-directive="1 + 1" 中，绑定值为 2。</li>
      <li>oldValue：指令绑定的前一个值，仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。</li>
      <li>expression：字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中，表达式为 "1 + 1"。</li>
      <li>arg：传给指令的参数，可选。例如 v-my-directive:foo 中，参数为 "foo"</li>
      <li>modifiers：一个包含修饰符的对象。例如：v-my-directive.foo.bar 中，修饰符对象为 { foo: true, bar: true }</li>
    </ul>
    <li>vnode：Vue 编译生成的虚拟节点</li>
    <li>oldVnode：上一个虚拟节点，仅在 update 和 componentUpdated 钩子中可用</li>
    <li>除了 el 之外，其它参数都应该是只读的，切勿进行修改。如果需要在钩子之间共享数据，建议通过元素的 dataset 来进行</li>
  </ul>
  <script>
    // <div v-demo="{ color: 'white', text: 'hello!' }"></div>
    // <script>
    //     Vue.directive('demo', function (el, binding) {
    //     console.log(binding.value.color) // "white"
    //     console.log(binding.value.text)  // "hello!"
    //     })
    // </script>
  </script>
  <h2>三、应用场景</h2>
  <div>使用自定义组件组件可以满足我们日常一些场景，这里给出几个自定义组件的案例：</div>
  <ul>
    <li>防抖</li>
    <li>图片懒加载</li>
    <li>一键 Copy的功能</li>
  </ul>
  <h4>输入框防抖</h4>
  <div>防抖这种情况设置一个v-throttle自定义指令来实现</div>
  <script>
    // 1.设置v-throttle自定义指令
    // Vue.directive('throttle', {
    //   bind: (el, binding) => {
    //     let throttleTime = binding.value; // 防抖时间
    //     if (!throttleTime) { // 用户若不设置防抖时间，则默认2s
    //       throttleTime = 2000;
    //     }
    //     let cbFun;
    //     el.addEventListener('click', event => {
    //       if (!cbFun) { // 第一次执行
    //         cbFun = setTimeout(() => {
    //           cbFun = null;
    //         }, throttleTime);
    //       } else {
    //         event && event.stopImmediatePropagation();
    //       }
    //     }, true);
    //   },
    // });
    // // 2.为button标签设置v-throttle自定义指令
    // <button @click="sayHello" v-throttle>提交</button>
  </script>
  <h4>图片懒加载</h4>
  <div>设置一个v-lazy自定义组件完成图片懒加载</div>
  <script>
    // const LazyLoad = {
    //     // install方法
    //     install(Vue,options){
    //       // 代替图片的loading图
    //         let defaultSrc = options.default;
    //         Vue.directive('lazy',{
    //             bind(el,binding){
    //                 LazyLoad.init(el,binding.value,defaultSrc);
    //             },
    //             inserted(el){
    //                 // 兼容处理
    //                 if('IntersectionObserver' in window){
    //                     LazyLoad.observe(el);
    //                 }else{
    //                     LazyLoad.listenerScroll(el);
    //                 }
                    
    //             },
    //         })
    //     },
    //     // 初始化
    //     init(el,val,def){
    //         // data-src 储存真实src
    //         el.setAttribute('data-src',val);
    //         // 设置src为loading图
    //         el.setAttribute('src',def);
    //     },
    //     // 利用IntersectionObserver监听el
    //     observe(el){
    //         let io = new IntersectionObserver(entries => {
    //             let realSrc = el.dataset.src;
    //             if(entries[0].isIntersecting){
    //                 if(realSrc){
    //                     el.src = realSrc;
    //                     el.removeAttribute('data-src');
    //                 }
    //             }
    //         });
    //         io.observe(el);
    //     },
    //     // 监听scroll事件
    //     listenerScroll(el){
    //         let handler = LazyLoad.throttle(LazyLoad.load,300);
    //         LazyLoad.load(el);
    //         window.addEventListener('scroll',() => {
    //             handler(el);
    //         });
    //     },
    //     // 加载真实图片
    //     load(el){
    //         let windowHeight = document.documentElement.clientHeight
    //         let elTop = el.getBoundingClientRect().top;
    //         let elBtm = el.getBoundingClientRect().bottom;
    //         let realSrc = el.dataset.src;
    //         if(elTop - windowHeight<0&&elBtm > 0){
    //             if(realSrc){
    //                 el.src = realSrc;
    //                 el.removeAttribute('data-src');
    //             }
    //         }
    //     },
    //     // 节流
    //     throttle(fn,delay){
    //         let timer; 
    //         let prevTime;
    //         return function(...args){
    //             let currTime = Date.now();
    //             let context = this;
    //             if(!prevTime) prevTime = currTime;
    //             clearTimeout(timer);
                
    //             if(currTime - prevTime > delay){
    //                 prevTime = currTime;
    //                 fn.apply(context,args);
    //                 clearTimeout(timer);
    //                 return;
    //             }

    //             timer = setTimeout(function(){
    //                 prevTime = Date.now();
    //                 timer = null;
    //                 fn.apply(context,args);
    //             },delay);
    //         }
    //     }

    // }
    // export default LazyLoad;
  </script>
  <h4>一键 Copy的功能</h4>
  <script>
    // import { Message } from 'ant-design-vue';

    // const vCopy = { //
    //   /*
    //     bind 钩子函数，第一次绑定时调用，可以在这里做初始化设置
    //     el: 作用的 dom 对象
    //     value: 传给指令的值，也就是我们要 copy 的值
    //   */
    //   bind(el, { value }) {
    //     el.$value = value; // 用一个全局属性来存传进来的值，因为这个值在别的钩子函数里还会用到
    //     el.handler = () => {
    //       if (!el.$value) {
    //       // 值为空的时候，给出提示，我这里的提示是用的 ant-design-vue 的提示，你们随意
    //         Message.warning('无复制内容');
    //         return;
    //       }
    //       // 动态创建 textarea 标签
    //       const textarea = document.createElement('textarea');
    //       // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘，同时将 textarea 移出可视区域
    //       textarea.readOnly = 'readonly';
    //       textarea.style.position = 'absolute';
    //       textarea.style.left = '-9999px';
    //       // 将要 copy 的值赋给 textarea 标签的 value 属性
    //       textarea.value = el.$value;
    //       // 将 textarea 插入到 body 中
    //       document.body.appendChild(textarea);
    //       // 选中值并复制
    //       textarea.select();
    //       // textarea.setSelectionRange(0, textarea.value.length);
    //       const result = document.execCommand('Copy');
    //       if (result) {
    //         Message.success('复制成功');
    //       }
    //       document.body.removeChild(textarea);
    //     };
    //     // 绑定点击事件，就是所谓的一键 copy 啦
    //     el.addEventListener('click', el.handler);
    //   },
    //   // 当传进来的值更新的时候触发
    //   componentUpdated(el, { value }) {
    //     el.$value = value;
    //   },
    //   // 指令与元素解绑的时候，移除事件绑定
    //   unbind(el) {
    //     el.removeEventListener('click', el.handler);
    //   },
    // };

    // export default vCopy;
  </script>
  <h4>关于自定义组件还有很多应用场景，如：拖拽指令、页面水印、权限校验等等应用场景</h4>
</body>
</html>